package rtc.core.net.rpc;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;

import com.coco.base.log.SLog;
import com.coco.base.utils.NetworkUtils;

import org.json.JSONObject;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

import rtc.core.net.CoreSocket;
import rtc.core.net.MsgBody;

/**
 * Created by Administrator on 2018/2/23.
 */

public class RpcManager {
    public static final long TIMEOUT_DEFAULT = 30 * 1000;
    private static final String TAG = "RpcManager";
    private static RpcManager INSTANCE = null;
    private final Map<String,CopyOnWriteArraySet<RpcManager.IComponent>> mComponentMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Integer,RpcEntity> mRequestMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Integer,TimeoutTask> mTimeoutTaskMap = new ConcurrentHashMap<>();
    private final Handler mWorkHandler;

    public interface IComponent{
        void handleRpcMessage(int appid,String fn,JSONObject args);
    }

    public static RpcManager getInstance(){
        if(INSTANCE == null){
            synchronized (RpcManager.class){
                if(INSTANCE == null){
                    INSTANCE = new RpcManager();
                }
            }
        }
        return INSTANCE;
    }

    private RpcManager(){
        CoreNet.getInstance().setDataCallback(mDataCallback);
        HandlerThread thread = new HandlerThread("RpcTimeout", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        mWorkHandler = new Handler(thread.getLooper());
    }


    void registerCommands(RpcManager.IComponent component,List<Command> commands){
        if(component == null || commands == null || commands.isEmpty()){
            return;
        }
        SLog.d(TAG,"registerCommands component = "+component+",commands = "+String.valueOf(commands));
        for(Command command : commands){
            final String key = command.appid + command.fn;
            CopyOnWriteArraySet<RpcManager.IComponent> set = mComponentMap.get(key);
            if(set == null){
                set = new CopyOnWriteArraySet<>();
                set.add(component);
                mComponentMap.put(key,set);
            }else{
                set.add(component);
            }
        }
    }

    void unregisterCommands(IComponent component,List<Command> commands){
        if(component == null || commands == null || commands.isEmpty()){
            return;
        }
        SLog.d(TAG,"unregisterCommands component = "+component+",commands = "+String.valueOf(commands));
        for(Command command : commands){
            final String key = command.appid + command.fn;
            CopyOnWriteArraySet<IComponent> valueSet = mComponentMap.get(key);
            if(valueSet != null && valueSet.size() > 0){
                valueSet.remove(component);
            }
        }
    }

    void unregisterCommandsAll(IComponent component){
        if(component == null){
            return;
        }
        SLog.d(TAG,"unregisterCommandsAll component = "+component);
        Set<String> keys = mComponentMap.keySet();
        for(String key : keys){
            CopyOnWriteArraySet<IComponent> valueSet = mComponentMap.get(key);
            if(valueSet != null && valueSet.size() > 0){
                valueSet.remove(component);
            }
        }
    }

    private final CoreNet.DataCallback mDataCallback = new CoreNet.DataCallback() {
        @Override
        public void onData(MsgBody msg) {
            try {
                if(msg.cb > 0){
                    RpcEntity entity = mRequestMap.remove(msg.cb);
                    SLog.i(TAG, " handle rpc call cb = %d, fn = '%s', appid = %d, arg = %s ", msg.cb, msg.fn, msg.appid, msg.arg);
                    final int code = msg.arg.getInt("result");
                    final String reason = msg.arg.optString("reason");
                    notifyCallback(entity,code,reason,msg.arg);
                }else{
                    final String key = msg.appid + msg.fn;
                    SLog.i(TAG, " handle rpc message cb = %d, fn = '%s', appid = %d, arg = %s ", msg.cb, msg.fn, msg.appid, msg.arg);
                    Set<IComponent> managerSet = mComponentMap.get(key);
                    if (managerSet == null || managerSet.isEmpty()) {
                        SLog.e(TAG, "RPC method name not found,fn = '%s',appid = %s",msg.fn,msg.appid);
                    } else {
                        for(IComponent component : managerSet){
                            try{
                                component.handleRpcMessage(msg.appid, msg.fn, msg.arg);
                            }catch (Exception e){
                                String log = String.format("%s error [%s] during invocation [fn=%s, arg=%s]",String.valueOf(component),e, msg.fn, msg.arg);
                                SLog.e(TAG, log, e);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                SLog.e(TAG,"mDataCallback onData Exception,MsgBody = "+String.valueOf(msg),e);
            }
        }
    };

    public RpcEntity rpcCallSync(int appid, String fn, int cb,Map arg){
        return rpcCallSync(appid,fn,cb,new JSONObject(arg),TIMEOUT_DEFAULT);
    }

    public RpcEntity rpcCallSync(int appid, String fn, int cb,JSONObject arg){
        return rpcCallSync(appid,fn,cb,arg,TIMEOUT_DEFAULT);
    }

    public RpcEntity rpcCallSync(int appid, String fn, int cb,Map arg,long timeout){
        return rpcCallSync(appid,fn,cb,new JSONObject(arg),timeout);
    }

    public RpcEntity rpcCallSync(int appid, String fn, int cb,JSONObject arg,long timeout){
        final RpcEntity[] result = new RpcEntity[]{new RpcEntity()};
        final CountDownLatch latch = new CountDownLatch(1);
        this.rpcCall(appid, fn, cb, arg, timeout, new RpcEntity.Callback() {
            @Override
            public void onCallback(int code, String msg, JSONObject args, RpcEntity response) {
                result[0] = response;
                latch.countDown();
            }
        });
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return result[0];
    }

    public void rpcCall(int appid, String fn, int cb,Map arg,RpcEntity.Callback callback){
        rpcCall(appid,fn,cb,new JSONObject(arg),TIMEOUT_DEFAULT,callback);
    }

    public void rpcCall(int appid, String fn, int cb,Map arg, long timeout, RpcEntity.Callback callback){
        rpcCall(appid,fn,cb,new JSONObject(arg),timeout,callback);
    }

    public void rpcCall(int appid, String fn, int cb,JSONObject arg,RpcEntity.Callback callback){
        rpcCall(appid,fn,cb,arg,TIMEOUT_DEFAULT,callback);
    }

    public void rpcCall(int appid, String fn, int cb,JSONObject arg,long timeout,RpcEntity.Callback callback){
        RpcEntity request = new RpcEntity();
        request.appid = appid;
        request.fn = fn;
        request.reqArgs = arg;
        request.timeout = timeout;
        request.callback = callback;
        request.cb = cb;
        try{
            if(!NetworkUtils.isConnected()){
                SLog.e(TAG, "send rpc request failed RPC_ERROR_NETWORK_UNABLE");
                notifyCallback(request,RpcEntity.RPC_ERROR_NETWORK_UNABLE,"网络不可用",null);
                return;
            }
            if(CoreNet.getInstance().state() != CoreSocket.STATE_CONNECTED){
                SLog.e(TAG, "send rpc request failed RPC_ERROR_NOT_LOGINED");
                notifyCallback(request,RpcEntity.RPC_ERROR_NOT_LOGINED,"网络连接错误",null);
                return;
            }
            byte[] requestPacket = request.buildRequest();
            SLog.i(TAG,"rpcCall requestPacket length = %s,request = %s",requestPacket.length,String.valueOf(request));
            CoreNet.getInstance().send(requestPacket, new CoreNet.SendCallback() {
                @Override
                public void onResult(int length) {
                    if(length <= 0){
                        SLog.e(TAG, "send rpc request failed RPC_ERROR_SEND_REQUEST");
                        notifyCallback(request,RpcEntity.RPC_ERROR_SEND_REQUEST,"发送请求错误",null);
                    }else{
                        mRequestMap.put(request.cb,request);
                        final TimeoutTask task = new TimeoutTask(request);
                        mTimeoutTaskMap.put(request.cb,task);
                        mWorkHandler.postDelayed(task,request.timeout);
                    }
                }
            });
        }catch (Exception e){
            e.printStackTrace();
            SLog.e(TAG,"rpcCall Exception,cb = "+cb,e);
            mRequestMap.remove(cb);
            notifyCallback(request,RpcEntity.RPC_ERROR_SEND_REQUEST,"发送请求错误",null);
        }
    }

    private void notifyCallback(RpcEntity response,int code,String msg,JSONObject respArgs){
        response.code = code;
        response.msg = msg;
        response.respArgs = respArgs;
        TimeoutTask task = mTimeoutTaskMap.get(response.cb);
        if(task != null){
            mWorkHandler.removeCallbacks(task);
        }
        response.callback.onCallback(code,msg,respArgs,response);
    }

    private class TimeoutTask implements Runnable{
        private final RpcEntity request;

        private TimeoutTask(RpcEntity request) {
            this.request = request;
        }

        @Override
        public void run() {
            SLog.w(TAG,"rpc request timeout,request = "+String.valueOf(request));
            mRequestMap.remove(request.cb);
            notifyCallback(request,RpcEntity.RPC_ERROR_TIMEOUT,"网络请求超时",null);
        }
    }

}
